package com.fzk.server;

import com.alibaba.fastjson2.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * 快速响应工具类
 *
 * @author fzk
 * @datetime 2023-02-14 01:05:35
 */
public class HttpResponseUtil {
    public static void sendError(ChannelHandlerContext ctx, boolean keepAlive, HttpResponseStatus status) {
        sendError(ctx, keepAlive, status, status.reasonPhrase());
    }

    public static void sendError(ChannelHandlerContext ctx, boolean keepAlive, HttpResponseStatus status, String msg) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                Unpooled.wrappedBuffer(String.format("Failure: %s \r\nError: %s", status, msg).getBytes(StandardCharsets.UTF_8)));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        sendAndCleanupConnection(ctx, keepAlive, response);
    }

    /**
     * If Keep-Alive is disabled, attaches "Connection: close" header to the response
     * and closes the connection after the response being sent.
     */
    public static void sendAndCleanupConnection(ChannelHandlerContext ctx, boolean keepAlive, FullHttpResponse response) {
        // 设置响应体长度
        HttpUtil.setContentLength(response, response.content().readableBytes());
        // 设置是否保持连接
        HttpUtil.setKeepAlive(response, keepAlive);
        // 这个response包含响应所有内容
        ChannelFuture flushPromise = ctx.writeAndFlush(response);

        if (!keepAlive) {
            // Close the connection as soon as the response is sent.
            flushPromise.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 输出响应并判断是否关闭连接
     *
     * @param ctx       上下文
     * @param keepAlive 是否保持长连接
     * @param response  响应行/头
     * @param body      响应体
     */
    public static void sendAndCleanupConnection(ChannelHandlerContext ctx, boolean keepAlive, DefaultHttpResponse response, ByteBuf body) {
        // 设置响应体类型
        if (response.headers().get(HttpHeaderNames.CONTENT_TYPE) == null) {
            throw new RuntimeException("响应头必须设置content-type");
        }
        // 设置响应体长度
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());
        // 设置是否保持连接
        HttpUtil.setKeepAlive(response, keepAlive);
        // 发送响应行/头
        ctx.write(response);
        // 发送响应体
        ctx.write(body);
        // 结束
        ChannelFuture flushPromise = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!keepAlive) {
            // Close the connection as soon as the response is sent.
            flushPromise.addListener(ChannelFutureListener.CLOSE);
        }
    }

    // 输出响应成功
    public static void success(ChannelHandlerContext ctx, boolean keepAlive, ByteBuf body, String contentType) {
        // 写响应
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, body);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        HttpResponseUtil.sendAndCleanupConnection(ctx, keepAlive, response);
    }

    public static void json(ChannelHandlerContext ctx, boolean keepAlive, Object result) {
        String str = JSON.toJSONString(result);
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(str.getBytes(StandardCharsets.UTF_8)));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
        MyHttpUtil.addCharsetToContentType(response);
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        HttpResponseUtil.sendAndCleanupConnection(ctx, keepAlive, response);
    }

    /**
     * 请求重定向 302/307
     * 浏览器将根据状态码和location响应头自动重定向
     */
    public static void sendReqRedirect(ChannelHandlerContext ctx, FullHttpRequest request, String redirectUrl) {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.TEMPORARY_REDIRECT, Unpooled.EMPTY_BUFFER);
        // 注意: 必须进行URL编码，因为路径可能有中文，而HTTP响应头仅支持ascii编码
        // 我服了，这个还会把 / 编码为 %2F
        redirectUrl = URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8).replaceAll("%2F", "/");
        response.headers().set(HttpHeaderNames.LOCATION, redirectUrl);// 浏览器地址栏请求可以自动跳转页面 status=302/307
        sendAndCleanupConnection(ctx, HttpUtil.isKeepAlive(request), response);// 响应浏览器
    }

    public static void sendPageRedirect(ChannelHandlerContext ctx, FullHttpRequest request, String redirectUrl) {
        sendPageRedirect(ctx, request,
                new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.TEMPORARY_REDIRECT, Unpooled.EMPTY_BUFFER),
                redirectUrl);
    }

    /**
     * 页面重定向 302/307 或 ajax 200
     * 前端一般两种请求，一种是浏览器地址访问，这种可以依赖浏览器自动重定向(如307码)实现页面跳转
     * 而ajax请求是是局部刷新，浏览器自动重定向只将该请求跳转，而不会跳转页面。可以通过前端代码约定识别指定响应头，手动跳转页面
     * 大多数JavaScript库（如jQuery、Axios等）在发送AJAX请求时，会自动在请求头中添加 X-Requested-With: XMLHttpRequest。
     * 如果是原生js写的请求，则需要前端手动加上这个请求头，以便后端识别。
     * 针对ajax请求重定向的前后端约定：status=200，响应头REDIRECT存在表示要重定向，CONTEXTPATH响应头指向重定向地址
     */
    public static void sendPageRedirect(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response, String redirectUrl) {
        // 注意: 必须进行URL编码，因为路径可能有中文，而HTTP响应头仅支持ascii编码
        // 我服了，这个还会把 / 编码为 %2F
        redirectUrl = URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8).replaceAll("%2F", "/");

        response.headers().set(HttpHeaderNames.LOCATION, redirectUrl);// 浏览器地址栏请求可以自动跳转页面 status=302/307

        if ("XMLHttpRequest".equals(request.headers().get(HttpHeaderNames.X_REQUESTED_WITH))) {// 识别ajax请求
            response.setStatus(HttpResponseStatus.OK);// http状态码设置200，由前端代码检测手动跳转
            response.headers().add("REDIRECT", "REDIRECT").add("CONTEXTPATH", redirectUrl);
        }
        sendAndCleanupConnection(ctx, HttpUtil.isKeepAlive(request), response);// 响应浏览器
    }
}
